/*
 * Copyright 2022 André Claßen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.burnoutcrew.reorderable

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemInfo
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalDensity
import kotlinx.coroutines.CoroutineScope

/**
 * 创建一个可重新排序的水平方向 LazyStaggeredGrid 的状态。
 *
 * @param onMove 当元素被拖拽到新位置时调用，提供原始位置和目标位置信息。
 * @param gridState 可选的基础 LazyStaggeredGridState，默认使用 [rememberLazyStaggeredGridState] 记住的状态。
 * @param canDragOver 判断是否允许将正在拖动的项放置在某个位置之上，默认为 null 表示全部允许。
 * @param onDragEnd 拖拽结束时回调，传入起始索引与最终索引。
 * @param maxScrollPerFrame 每帧最大滚动距离（像素），默认为 20f。
 * @param dragCancelledAnimation 拖拽取消后的动画效果，默认使用弹簧动画。
 * @return 返回配置好的 [ReorderableLazyStaggeredGridState] 实例。
 */
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun rememberReorderableLazyHorizontalStaggeredGridState(
    onMove: (ItemPosition, ItemPosition) -> Unit,
    gridState: LazyStaggeredGridState = rememberLazyStaggeredGridState(),
    canDragOver: ((draggedOver: ItemPosition, dragging: ItemPosition) -> Boolean)? = null,
    onDragEnd: ((startIndex: Int, endIndex: Int) -> (Unit))? = null,
    maxScrollPerFrame: Float = 20f,
    dragCancelledAnimation: DragCancelledAnimation = SpringDragCancelledAnimation(),
) = rememberReorderableLazyStaggeredGridState(
    onMove = onMove,
    gridState = gridState,
    canDragOver = canDragOver,
    onDragEnd = onDragEnd,
    maxScrollPerFrame = maxScrollPerFrame,
    dragCancelledAnimation = dragCancelledAnimation,
    orientation = Orientation.Horizontal
)

/**
 * 创建一个可重新排序的垂直方向 LazyStaggeredGrid 的状态。
 *
 * @param onMove 当元素被拖拽到新位置时调用，提供原始位置和目标位置信息。
 * @param gridState 可选的基础 LazyStaggeredGridState，默认使用 [rememberLazyStaggeredGridState] 记住的状态。
 * @param canDragOver 判断是否允许将正在拖动的项放置在某个位置之上，默认为 null 表示全部允许。
 * @param onDragEnd 拖拽结束时回调，传入起始索引与最终索引。
 * @param maxScrollPerFrame 每帧最大滚动距离（像素），默认为 20f。
 * @param dragCancelledAnimation 拖拽取消后的动画效果，默认使用弹簧动画。
 * @return 返回配置好的 [ReorderableLazyStaggeredGridState] 实例。
 */
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun rememberReorderableLazyVerticalStaggeredGridState(
    onMove: (ItemPosition, ItemPosition) -> Unit,
    gridState: LazyStaggeredGridState = rememberLazyStaggeredGridState(),
    canDragOver: ((draggedOver: ItemPosition, dragging: ItemPosition) -> Boolean)? = null,
    onDragEnd: ((startIndex: Int, endIndex: Int) -> (Unit))? = null,
    maxScrollPerFrame: Float = 20f,
    dragCancelledAnimation: DragCancelledAnimation = SpringDragCancelledAnimation(),
) = rememberReorderableLazyStaggeredGridState(
    onMove = onMove,
    gridState = gridState,
    canDragOver = canDragOver,
    onDragEnd = onDragEnd,
    maxScrollPerFrame = maxScrollPerFrame,
    dragCancelledAnimation = dragCancelledAnimation,
    orientation = Orientation.Vertical
)

/**
 * 创建一个通用的可重新排序的 LazyStaggeredGrid 状态。
 *
 * @param onMove 当元素被拖拽到新位置时调用，提供原始位置和目标位置信息。
 * @param gridState 基础的 LazyStaggeredGridState。
 * @param canDragOver 判断是否允许将正在拖动的项放置在某个位置之上。
 * @param onDragStart 拖拽开始时回调，传入起始索引以及触摸点坐标。
 * @param onDragEnd 拖拽结束时回调，传入起始索引与最终索引。
 * @param maxScrollPerFrame 每帧最大滚动距离（像素）。
 * @param dragCancelledAnimation 拖拽取消后的动画效果。
 * @param orientation 排列方向：水平或垂直。
 * @return 返回配置好的 [ReorderableLazyStaggeredGridState] 实例。
 */
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun rememberReorderableLazyStaggeredGridState(
    onMove: (ItemPosition, ItemPosition) -> Unit,
    gridState: LazyStaggeredGridState = rememberLazyStaggeredGridState(),
    canDragOver: ((draggedOver: ItemPosition, dragging: ItemPosition) -> Boolean)? = null,
    onDragStart: ((startIndex: Int, x: Int, y: Int) -> (Unit))? = null,
    onDragEnd: ((startIndex: Int, endIndex: Int) -> (Unit))? = null,
    maxScrollPerFrame: Float = 20F,
    dragCancelledAnimation: DragCancelledAnimation = SpringDragCancelledAnimation(),
    orientation: Orientation
): ReorderableLazyStaggeredGridState {
    val maxScroll = with(LocalDensity.current) { maxScrollPerFrame }
    val scope = rememberCoroutineScope()
    val state = remember(gridState) {
        ReorderableLazyStaggeredGridState(
            gridState=gridState,
            scope = scope,
            maxScrollPerFrame = maxScrollPerFrame,
            onMove = onMove,
            onDragStart = onDragStart,
            canDragOver = canDragOver,
            onDragEnd = onDragEnd,
            dragCancelledAnimation = dragCancelledAnimation,
            orientation = orientation
        )
    }

    // 监听可见项变化并触发一次空拖拽以更新布局
    LaunchedEffect(state) {
        state.visibleItemsChanged()
            .collect { state.onDrag(0, 0) }
    }

    // 处理自动滚动逻辑
    LaunchedEffect(state) {
        while (true) {
            val diff = state.scrollChannel.receive()
            gridState.scrollBy(diff)
        }
    }
    return state
}

/**
 * 支持拖拽重排功能的 LazyStaggeredGrid 状态管理器。
 *
 * @property gridState 底层的 LazyStaggeredGridState。
 * @property scope 协程作用域。
 * @property maxScrollPerFrame 每帧最大滚动速度。
 * @property onMove 元素移动事件处理函数。
 * @property canDragOver 控制哪些位置可以接受拖放操作。
 * @property onDragStart 拖拽开始监听器。
 * @property onDragEnd 拖拽完成监听器。
 * @property dragCancelledAnimation 拖拽中断后执行的动画。
 * @property orientation 排列方向。
 */
@OptIn(ExperimentalFoundationApi::class)
class ReorderableLazyStaggeredGridState(
    val gridState: LazyStaggeredGridState,
    scope: CoroutineScope,
    maxScrollPerFrame: Float,
    onMove: (fromIndex: ItemPosition, toIndex: ItemPosition) -> (Unit),
    canDragOver: ((draggedOver: ItemPosition, dragging: ItemPosition) -> Boolean)? = null,
    onDragStart: ((startIndex: Int, x: Int, y: Int) -> (Unit))? = null,
    onDragEnd: ((startIndex: Int, endIndex: Int) -> (Unit))? = null,
    dragCancelledAnimation: DragCancelledAnimation = SpringDragCancelledAnimation(),
    val orientation: Orientation
) : ReorderableState<LazyStaggeredGridItemInfo>(
    scope = scope,
    maxScrollPerFrame = maxScrollPerFrame,
    onMove = onMove,
    onDragStart = onDragStart,
    canDragOver = canDragOver,
    onDragEnd = onDragEnd,
    dragCancelledAnimation = dragCancelledAnimation
) {
    /**
     * 是否是纵向滚动。
     */
    override val isVerticalScroll: Boolean
        get() = orientation == Orientation.Vertical // XXX gridState.isVertical is not accessible

    /**
     * 获取当前项左边距。
     */
    override val LazyStaggeredGridItemInfo.left: Int
        get() = offset.x

    /**
     * 获取当前项右边距。
     */
    override val LazyStaggeredGridItemInfo.right: Int
        get() = offset.x + size.width

    /**
     * 获取当前项上边距。
     */
    override val LazyStaggeredGridItemInfo.top: Int
        get() = offset.y

    /**
     * 获取当前项下边距。
     */
    override val LazyStaggeredGridItemInfo.bottom: Int
        get() = offset.y + size.height

    /**
     * 获取当前项宽度。
     */
    override val LazyStaggeredGridItemInfo.width: Int
        get() = size.width

    /**
     * 获取当前项高度。
     */
    override val LazyStaggeredGridItemInfo.height: Int
        get() = size.height

    /**
     * 获取当前项索引。
     */
    override val LazyStaggeredGridItemInfo.itemIndex: Int
        get() = index

    /**
     * 获取当前项键值。
     */
    override val LazyStaggeredGridItemInfo.itemKey: Any
        get() = key

    /**
     * 获取所有可见项的信息列表。
     */
    override val visibleItemsInfo: List<LazyStaggeredGridItemInfo>
        get() = gridState.layoutInfo.visibleItemsInfo

    /**
     * 视口起始偏移量。
     */
    override val viewportStartOffset: Int
        get() = gridState.layoutInfo.viewportStartOffset

    /**
     * 视口结束偏移量。
     */
    override val viewportEndOffset: Int
        get() = gridState.layoutInfo.viewportEndOffset

    /**
     * 第一个可见项的索引。
     */
    override val firstVisibleItemIndex: Int
        get() = gridState.firstVisibleItemIndex

    /**
     * 第一个可见项相对于视口顶部的偏移量。
     */
    override val firstVisibleItemScrollOffset: Int
        get() = gridState.firstVisibleItemScrollOffset

    /**
     * 滚动到指定索引的项目，并设置偏移量。
     *
     * @param index 要滚动到的目标项目的索引。
     * @param offset 相对于该项目顶部的额外偏移量。
     */
    override suspend fun scrollToItem(index: Int, offset: Int) {
        gridState.scrollToItem(index, offset)
    }
}
